package edu.pitt.cs.cs2410pj2.core;

import java.util.ArrayList;

import edu.pitt.cs.cs2410pj2.datamodel.L1CacheEntry;
import edu.pitt.cs.cs2410pj2.datamodel.L1CacheSet;
import edu.pitt.cs.cs2410pj2.datamodel.Message;
import edu.pitt.cs.cs2410pj2.datamodel.MessageBus;
import edu.pitt.cs.cs2410pj2.datamodel.MessageType;
import edu.pitt.cs.cs2410pj2.util.AddrInfo;
import edu.pitt.cs.cs2410pj2.util.AddrProcessor;
import edu.pitt.cs.cs2410pj2.util.Parameters;

/**
 * The L1Cache structure
 * 
 * Basically maintains a bunch of cache sets
 * 
 * @author zhangfan
 * 
 */
public class L1Cache {
	private Parameters para;
	private int tileId;

	int hitCount = 0;
	int missCount = 0;
	
	private ArrayList<L1CacheSet> sets = new ArrayList<L1CacheSet>();
	
	public Parameters getPara() {
		return para;
	}

	public void setPara(Parameters para) {
		this.para = para;
	}

	public int getTileId() {
		return tileId;
	}

	public void setTileId(int tileId) {
		this.tileId = tileId;
	}

	public ArrayList<L1CacheSet> getSets() {
		return sets;
	}

	public void setSets(ArrayList<L1CacheSet> sets) {
		this.sets = sets;
	}

	// actually this method might not be useful
	public L1CacheSet getCacheSet(int setIndex) {
		return sets.get(setIndex);
	}

	public L1Cache(Parameters para, int tileId) {
		this.para = para;
		this.tileId = tileId;
		int m = para.n1 - para.a1 - para.b;
		int setNum = (int) Math.pow(2, m);
		for (int i = 0; i < setNum; i++) {
			L1CacheSet set = new L1CacheSet(para,tileId, i);
			sets.add(set);
		}
	}

	// Processing logic when L1 receives a message
	// Cases could be:
	// Remote node or local node sends the data
	public void receive(Message msg, int timeCycle, MessageBus bus) {
//		if(timeCycle>=2930) {
//			System.out.println("DDDDD");
//			System.err.println(msg);
//			try {
//				Thread.sleep(1000);
//			} catch (InterruptedException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
//		}
		String blockAddr = msg.getBlockAddr();
		AddrInfo addrInfo = AddrProcessor.processAddr(blockAddr, para.p,
				para.n1, para.a1, para.b);
		L1CacheEntry block = addBlock(addrInfo.getSetIndex(),
				addrInfo.getCacheTag(), timeCycle, msg.getData(),msg.getBlockAddr());
		if (block != null) {
			writeBackData(block, block.getBlockAddr(), bus);
		} else {
		//System.out.println("NOT REPLACED");
		}
	}

	// L1 sends the data back to write back data to L2
	public void writeBackData(L1CacheEntry block, String blockAddr,
			MessageBus bus) {
		Message msg = new Message();
		msg.setBlockAddr(blockAddr);
		msg.setData(block.getData());
		msg.setIsData(true);
		msg.setType(MessageType.DATAWRITEBACK);
		msg.setFrom(tileId);
		msg.setTo(AddrProcessor.processAddr(blockAddr, para.p, para.n2,
				para.a2, para.b).getTileId());
		bus.sendMessage(msg);
	}

	// change the status of the block
	public void changeBlockStatus(int setIndex, int cacheTag, int state) {
		sets.get(setIndex).findBlock(cacheTag).setState(state);
	}
	
	// change the status of the block with given address
	public void changeBlockStatus(String blockAddr, int state) {
		AddrInfo info = AddrProcessor.processAddr(blockAddr, para.p, para.n1, para.a1, para.b);
		changeBlockStatus(info.getSetIndex(),info.getCacheTag(),state);
	}
	

	// add a block, if the set is full, send back the entry, and prepare for the
	// CMP to write back
	public L1CacheEntry addBlock(int setIndex, int cacheTag, int timeCycle,
			String data, String blockAddr) {
		if (sets.get(setIndex).isFull()) {
			L1CacheEntry entry = sets.get(setIndex).replaceLRU(cacheTag,
					timeCycle, data,blockAddr);
			return entry;
		} else {
			sets.get(setIndex).addBlock(cacheTag, timeCycle, data,blockAddr);
		}
		return null;
	}
	//add block with given block address
	public L1CacheEntry addBlock(String blockAddr, int timeCycle,
			String data) {
		AddrInfo info = AddrProcessor.processAddr(blockAddr, para.p, para.n1, para.a1, para.b);
		return addBlock(info.getSetIndex(),info.getCacheTag(),timeCycle,data,blockAddr);
	}
	
	//Find a block with specified setIndex and cacheTag
	public L1CacheEntry findBlock(int setIndex, int cacheTag) {
		return sets.get(setIndex).findBlock(cacheTag);
	}
	
	//Find a block with the block Addr
	public L1CacheEntry findBlock(String blockAddr) {
		AddrInfo info = AddrProcessor.processAddr(blockAddr, para.p, para.n1, para.a1, para.b);
		return findBlock(info.getSetIndex(),info.getCacheTag());
	}
	
	//See if the block has the block address or not
	public boolean isBlockCached(String blockAddr) {
		if(findBlock(blockAddr)==null) return false;
		return true;
	}
	
	//update time
	public void updateTime(String blockAddr,int timeCycle) {
		findBlock(blockAddr).setUpdateTime(timeCycle);
	}
	public String toString() {
		String msg = "";
		for(int i = 0;i<this.sets.size();i++) {
			L1CacheSet cacheSet = this.sets.get(i);
			msg += cacheSet.toString();
		}
		return msg;
	}
}
